﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Net;
using System.Xml.Linq;
using System.Web;
using System.Windows;
using System.Text.RegularExpressions;

namespace Scatter
{
    public enum ConfirmMode
    {
        ForceMode,
        SignalMode
    }

    public class ResponseContextContainer
    {
        public ResponseContext response = null;
    }

    public class KeepAliveContext
    {
        public KeepAliveContext(string cookie, string url, string postData)
        {
            Cookie = cookie;
            Url = url;
            PostData = postData;
        }

        public string Cookie { get; private set; }
        public string Url { get; private set; }
        public string PostData { get; private set; }
    }

    public class AutoClick
    {
        private const string selectStoreUrlTemp = @"http://reserve.apple.com/WebObjects/ProductReservation.woa/wa/reserveProduct?lang=en&country={0}&prelaunch={1}";
        private const string renderCartUrlTemp = @"http://reserve.apple.com/WebObjects/ProductReservation.woa/wa/GianduiaAction/renderCart?lang=en&country={0}";
        private const string createReservationUrlTemp = @"http://reserve.apple.com/WebObjects/ProductReservation.woa/wa/GianduiaAction/createReservation?lang=en&country={0}";
        private const string addToCartPostDataTemp = @"skuID={0}A&planID=null&lang=en&country={1}";

        private ConfirmMode confirmMode;
        private bool canSuspend;

        public AutoClick(ConfirmMode confirmMode, bool canSuspend)
        {
            this.confirmMode = confirmMode;
            this.canSuspend = canSuspend;
        }

        private string cookie = string.Empty;

        private KeepAliveContext keepAliveContext;
        private string confirmPostData;
        private string confirmAction;

        private ManualResetEventSlim scatterDoor = new ManualResetEventSlim(false);
        private static ManualResetEventSlim pickDateDoor = new ManualResetEventSlim(false);
        private static ManualResetEventSlim checkProductDoor = new ManualResetEventSlim(false);
        private static ManualResetEventSlim suspendDoor = new ManualResetEventSlim(false);
        private static string[] timeAndMark = null;

        private ResponseContext ScatterRequest(RequestContext request)
        {
            ResponseContextContainer containerObj = null;
            while (true)
            {
                scatterDoor.Reset();
                containerObj = new ResponseContextContainer();

                for (int i = 0; i < ConfigHelper.Concurrent; i++)
                {
                    Thread worker = new Thread(
                        (object obj) =>
                        {
                            ResponseContextContainer container = obj as ResponseContextContainer;
                            while (true)
                            {
                                if (canSuspend == true)
                                {
                                    suspendDoor.Wait();
                                }

                                ResponseContext response = null;
                                while (true)
                                {
                                    if (container.response != null)
                                    {
                                        return;
                                    }

                                    try
                                    {
                                        response = Helper.GetResponse(request);
                                        break;
                                    }
                                    catch
                                    { 
                                    }
                                }

                                bool validResponse = false;
                                if (response.StatusCode == HttpStatusCode.OK)
                                {
                                    validResponse = true;
                                }

                                if (response.StatusCode == HttpStatusCode.Redirect)
                                {
                                    string location = response.Headers.Get("location");
                                    if (location.Contains("/WebObjects/ProductReservation.woa/wo") == true)
                                    {
                                        validResponse = true;
                                    }
                                }

                                if (validResponse == true)
                                {
                                    if (container.response == null)
                                    {
                                        lock (container)
                                        {
                                            if (container.response == null)
                                            {
                                                container.response = response;
                                                scatterDoor.Set();
                                            }
                                        }
                                    }
                                    return;
                                }
                            }
                        }
                    );
                    worker.Start(containerObj);
                }

                scatterDoor.Wait();

                if (containerObj.response.StatusCode == HttpStatusCode.OK)
                {
                    break;
                }

                if (containerObj.response.StatusCode == HttpStatusCode.Redirect)
                {
                    request = new RequestContext();
                    request.RequestUrl = containerObj.response.Headers.Get("location").Replace("https://", "http://");
                    request.AllowAutoRedirect = false;
                    request.Method = "GET";
                    request.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
                    request.Headers.Add("Accept-Encoding", "gzip,deflate,sdch");
                    request.Headers.Add("Accept-Language", "en-US,en;q=0.8");
                    request.Headers.Add("Cookie", this.cookie);
                }
            }

            if (this.cookie != string.Empty)
            {
                bool isTimeout = Helper.CheckSessionTimeout(this.cookie, containerObj.response.Headers.Get("set-cookie"));
                if (isTimeout == true)
                {
                    throw new TimeoutException();
                }
            }

            return containerObj.response;
        }

        private string InitialStep()
        {
            RequestContext request = new RequestContext();
            request.RequestUrl = string.Format(selectStoreUrlTemp, ConfigHelper.CurrentCountry, ConfigHelper.CurrentPrelaunchID);
            request.AllowAutoRedirect = false;
            request.Method = "GET";
            request.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
            request.Headers.Add("Accept-Encoding", "gzip,deflate,sdch");
            request.Headers.Add("Accept-Language", "en-US,en;q=0.8");
            request.Headers.Add("Cookie", "0");

            ResponseContext response = ScatterRequest(request);

            if (response.Content.Contains("id=\"isCartEmpty\"") == false)
            {
                throw new Exception();
            }

            string cookieString = response.Headers.Get("set-cookie");
            int index;
            index = cookieString.IndexOf("NSC_");
            string nscCookieName = cookieString.Substring(index, cookieString.IndexOf('=', index) - index);
            this.cookie = Helper.GetCookie(cookieString, new List<string>() { "RPRCookie", "wosid", "woinst", nscCookieName });

            return response.Content;
        }

        private string SelectStoreStep(string responseContent)
        {
            List<string> postDataIds = new List<string>() { "id=\"isCartEmpty\"", "id=\"productImage\"", "id=\"defaultStoreImage\"" };
            string selectStorePostData = string.Empty;
            int index;
            int beginIndex;
            string domHTML;
            XDocument dom;
            foreach (var item in postDataIds)
            {
                index = responseContent.IndexOf(item);
                while (responseContent[index] != '<')
                {
                    index--;
                }

                beginIndex = index;

                while (responseContent[index] != '>')
                {
                    index++;
                }

                domHTML = responseContent.Substring(beginIndex, index - beginIndex + 1);
                dom = XDocument.Parse(domHTML);
                selectStorePostData = selectStorePostData + dom.Root.Attribute("name").Value + "=" + HttpUtility.UrlEncode(dom.Root.Attribute("value").Value) + "&";
            }
            selectStorePostData = selectStorePostData + "storeNumber=" + ConfigHelper.CurrentStoreID;

            index = responseContent.IndexOf("id=\"TheForm\"");
            while (responseContent[index] != '<')
            {
                index--;
            }

            beginIndex = index;

            while (responseContent[index] != '>')
            {
                index++;
            }

            domHTML = responseContent.Substring(beginIndex, index - beginIndex) + "/>";
            dom = XDocument.Parse(domHTML);
            string selectStoreAction = "http://reserve.apple.com" + dom.Root.Attribute("action").Value;

            this.keepAliveContext = new KeepAliveContext(this.cookie, selectStoreAction, selectStorePostData);

            RequestContext request = new RequestContext();
            request.RequestUrl = selectStoreAction;
            request.AllowAutoRedirect = false;
            request.Method = "POST";
            request.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
            request.ContentType = "application/x-www-form-urlencoded";
            request.PostData = selectStorePostData;
            request.Headers.Add("Accept-Encoding", "gzip,deflate,sdch");
            request.Headers.Add("Accept-Language", "en-US,en;q=0.8");
            request.Headers.Add("Cookie", this.cookie);

            ResponseContext response = ScatterRequest(request);

            return response.Content;
        }

        private void AddToCartStep(bool scatterWay)
        {
            RequestContext request = new RequestContext();
            request.RequestUrl = "http://reserve.apple.com/WebObjects/ProductReservation.woa/wa/GianduiaAction/addToCart";
            request.AllowAutoRedirect = false;
            request.Method = "POST";
            request.Accept = "*/*";
            request.ContentType = "application/x-www-form-urlencoded";
            request.PostData = string.Format(addToCartPostDataTemp, ConfigHelper.CurrentProductID, ConfigHelper.CurrentCountry);
            request.Headers.Add("Accept-Encoding", "gzip,deflate,sdch");
            request.Headers.Add("Accept-Language", "en-US,en;q=0.8");
            request.Headers.Add("Cookie", this.cookie);

            if (scatterWay == true)
            {
                ScatterRequest(request);
            }
            else
            {
                ResponseContext response = null;
                while (true)
                {
                    while (true)
                    {
                        try
                        {
                            response = Helper.GetResponse(request);
                            break;
                        }
                        catch
                        { 
                        }
                    }
                    if (response.StatusCode == HttpStatusCode.OK)
                    {
                        break;
                    }
                }

                bool isTimeout = Helper.CheckSessionTimeout(this.cookie, response.Headers.Get("set-cookie"));
                if (isTimeout == true)
                {
                    throw new TimeoutException();
                }
            }
        }

        private string CreateReservationStep()
        {
            RequestContext request = new RequestContext();
            request.RequestUrl = string.Format(createReservationUrlTemp, ConfigHelper.CurrentCountry);
            request.AllowAutoRedirect = false;
            request.Method = "GET";
            request.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
            request.Headers.Add("Accept-Encoding", "gzip,deflate,sdch");
            request.Headers.Add("Accept-Language", "en-US,en;q=0.8");
            request.Headers.Add("Cookie", this.cookie);

            ResponseContext response = ScatterRequest(request);

            return response.Content;
        }

        private string LoginStep(string responseContent)
        {
            int index;
            int beginIndex;
            string domHTML;
            XDocument dom;

            index = responseContent.IndexOf("id=\"TheForm\"");
            while (responseContent[index] != '<')
            {
                index--;
            }

            beginIndex = index;

            while (responseContent[index] != '>')
            {
                index++;
            }

            domHTML = responseContent.Substring(beginIndex, index - beginIndex) + "/>";
            dom = XDocument.Parse(domHTML);
            string loginAction = "http://reserve.apple.com" + dom.Root.Attribute("action").Value;

            List<string> usernameAndPasswordIds = new List<string>() { "id=\"appleid\"", "id=\"password\"" };
            string loginPostData = string.Empty;
            foreach (var item in usernameAndPasswordIds)
            {
                index = responseContent.IndexOf(item);
                while (responseContent[index] != '<')
                {
                    index--;
                }

                beginIndex = index;

                while (responseContent[index] != '>')
                {
                    index++;
                }

                domHTML = responseContent.Substring(beginIndex, index - beginIndex + 1);
                dom = XDocument.Parse(domHTML);
                string value = string.Empty;
                if (item == "id=\"appleid\"")
                {
                    value = ConfigHelper.AppleID;
                }
                else
                {
                    value = ConfigHelper.Password;
                }
                loginPostData = loginPostData + dom.Root.Attribute("name").Value + "=" + HttpUtility.UrlEncode(value) + "&";
            }

            index = responseContent.IndexOf("id=\"fwdButtonC\"");
            while (responseContent[index] != '<')
            {
                index--;
            }
            index--;
            while (responseContent[index] != '<')
            {
                index--;
            }

            beginIndex = index;

            while (responseContent[index] != '>')
            {
                index++;
            }

            domHTML = responseContent.Substring(beginIndex, index - beginIndex) + "/>";
            dom = XDocument.Parse(domHTML);
            loginPostData = loginPostData + dom.Root.Attribute("name").Value + "=true";

            RequestContext request = new RequestContext();
            request.RequestUrl = loginAction;
            request.AllowAutoRedirect = false;
            request.Method = "POST";
            request.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
            request.ContentType = "application/x-www-form-urlencoded";
            request.PostData = loginPostData;
            request.Headers.Add("Accept-Encoding", "gzip,deflate,sdch");
            request.Headers.Add("Accept-Language", "en-US,en;q=0.8");
            request.Headers.Add("Cookie", this.cookie);

            ResponseContext response = ScatterRequest(request);

            return response.Content;
        }

        private string[] PickupDateStep()
        {
            RequestContext request = new RequestContext();
            request.RequestUrl = "http://reserve.apple.com/WebObjects/ProductReservation.woa/wa/GianduiaAction/fetchPickupDateTimeDetail";
            request.AllowAutoRedirect = false;
            request.Method = "POST";
            request.Accept = "*/*";
            request.ContentType = "application/x-www-form-urlencoded";
            request.PostData = string.Format("lang=en&country={0}", ConfigHelper.CurrentCountry);
            request.Headers.Add("Accept-Encoding", "gzip,deflate,sdch");
            request.Headers.Add("Accept-Language", "en-US,en;q=0.8");
            request.Headers.Add("Cookie", this.cookie);

            ResponseContext response = ScatterRequest(request);

            string responseContent = response.Content;

            int index;
            int beginIndex;
            int nextSearchIndex;
            int searchEndIndex;

            index = responseContent.IndexOf("timeslot :");
            if (index == -1)
            {
                return null;
            }

            while (responseContent.Substring(index).StartsWith("timestamp") == false)
            {
                index--;
            }
            while (responseContent[index] != '\'')
            {
                index++;
            }
            index++;
            beginIndex = index;
            while (responseContent[index] != '\'')
            {
                index++;
            }
            string timestamp = responseContent.Substring(beginIndex, index - beginIndex);

            while (responseContent.Substring(index).StartsWith("showPickupTimeSlots") == false)
            {
                index++;
            }
            searchEndIndex = index;

            List<string[]> times = new List<string[]>();
            nextSearchIndex = responseContent.IndexOf("timeslot :");
            while (true)
            {
                index = responseContent.IndexOf("timeslot :", nextSearchIndex);
                if (index == -1 || index > searchEndIndex)
                {
                    break;
                }

                while (responseContent[index] != '\'')
                {
                    index++;
                }
                index++;
                beginIndex = index;
                while (responseContent[index] != '\'')
                {
                    index++;
                }
                nextSearchIndex = index;
                string time = responseContent.Substring(beginIndex, index - beginIndex);

                while (responseContent.Substring(index).StartsWith("x-coredata:") == false)
                {
                    index--;
                }
                beginIndex = index;
                while (responseContent[index] != '\'')
                {
                    index++;
                }
                string timeUrl = responseContent.Substring(beginIndex, index - beginIndex);
                times.Add(new string[2] { time, timeUrl });
            }

            string pickupTime = null;
            foreach (var item in times)
            {
                DateTimeOffset wantedTime = DateTimeOffset.Parse("3:57 PM");
                string[] timeRange = item[0].Split('-');
                DateTimeOffset maxTime;
                if (timeRange.Length == 1)
                {
                    maxTime = DateTimeOffset.Parse(timeRange[0]);
                }
                else
                {
                    maxTime = DateTimeOffset.Parse(timeRange[1]);
                }
                if (wantedTime < maxTime)
                {
                    pickupTime = item[1];
                    break;
                }
            }

            if (pickupTime == null)
            {
                pickupTime = times.Last()[1];
            }

            return new string[2] { timestamp, pickupTime };
        }

        private void ConfirmStep(string responseContent, string[] timeAndMark)
        {
            int index;
            int beginIndex;
            string domHTML;
            XDocument dom;

            List<string> confirmIds = new List<string>() { "id=\"selectedDateValue\"", "id=\"selectedTimeValue\"", "id=\"selectedPickupStore\"", "id=\"showDates\"", "id=\"govtIDCheckEnabled\"", "id=\"selectedGovtIDValue\"", "id=\"captchaTextEntered\"", "id=\"userSelectedDateValue\"", "id=\"userSelectedDateValueOne\"", "id=\"userSelectedDateValueTwo\"" };
            string tempConfirmPostData = string.Empty;
            bool needGovernmentID = true;
            foreach (var item in confirmIds)
            {
                index = responseContent.IndexOf(item);
                while (responseContent[index] != '<')
                {
                    index--;
                }

                beginIndex = index;

                while (responseContent[index] != '>')
                {
                    index++;
                }

                domHTML = responseContent.Substring(beginIndex, index - beginIndex + 1);
                dom = XDocument.Parse(domHTML);
                string value = string.Empty;
                switch (item)
                {
                    case "id=\"selectedTimeValue\"":
                        value = timeAndMark[1];
                        break;
                    case "id=\"userSelectedDateValueOne\"":
                        value = timeAndMark[0];
                        break;
                    case "id=\"selectedGovtIDValue\"":
                        if (needGovernmentID == true)
                        {
                            value = ConfigHelper.GovernmentID;
                        }
                        break;
                    default:
                        if (dom.Root.Attribute("value") != null)
                        {
                            value = dom.Root.Attribute("value").Value;
                        }
                        if (item == "id=\"govtIDCheckEnabled\"")
                        {
                            if (value == "N")
                            {
                                needGovernmentID = false;
                            }
                        }
                        break;
                }
                tempConfirmPostData = tempConfirmPostData + dom.Root.Attribute("name").Value + "=" + HttpUtility.UrlEncode(value) + "&";
            }

            index = responseContent.IndexOf("id=\"fwdButtonC\"");
            while (responseContent[index] != '<')
            {
                index--;
            }
            index--;
            while (responseContent[index] != '<')
            {
                index--;
            }

            beginIndex = index;

            while (responseContent[index] != '>')
            {
                index++;
            }

            domHTML = responseContent.Substring(beginIndex, index - beginIndex) + "/>";
            dom = XDocument.Parse(domHTML);

            this.confirmPostData = tempConfirmPostData + dom.Root.Attribute("name").Value + "=true";

            index = responseContent.IndexOf("id=\"TheForm\"");
            while (responseContent[index] != '<')
            {
                index--;
            }

            beginIndex = index;

            while (responseContent[index] != '>')
            {
                index++;
            }

            domHTML = responseContent.Substring(beginIndex, index - beginIndex) + "/>";
            dom = XDocument.Parse(domHTML);
            this.confirmAction = "http://reserve.apple.com" + dom.Root.Attribute("action").Value;

            List<Thread> confirmThreads = new List<Thread>();
            for (int i = 0; i < ConfigHelper.ConfirmConcurrent; i++)
            {
                Thread thread = new Thread(DoConfirm, 512000);
                thread.Start();
                confirmThreads.Add(thread);
            }

            foreach (var item in confirmThreads)
            {
                item.Join();
            }
        }

        private void DoConfirm()
        {
            RequestContext request = new RequestContext();
            request.RequestUrl = this.confirmAction;
            request.AllowAutoRedirect = false;
            request.Method = "POST";
            request.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
            request.ContentType = "application/x-www-form-urlencoded";
            request.PostData = this.confirmPostData;
            request.Headers.Add("Accept-Encoding", "gzip,deflate,sdch");
            request.Headers.Add("Accept-Language", "en-US,en;q=0.8");
            request.Headers.Add("Cookie", this.cookie);

            while (true)
            {
                ResponseContext response = null;
                while (true)
                {
                    try
                    {
                        response = Helper.GetResponse(request);
                        break;
                    }
                    catch
                    { 
                    }
                }

                if (response.StatusCode == HttpStatusCode.OK)
                {
                    if (response.Headers.Get("set-cookie").Contains("wosid=;") == true)
                    {
                        AutoClickManager.GotIt();
                    }
                    break;
                }
            }
        }

        private void KeepSeesionAlive()
        {
            RequestContext request = new RequestContext();
            request.RequestUrl = this.keepAliveContext.Url;
            request.AllowAutoRedirect = false;
            request.Method = "POST";
            request.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
            request.ContentType = "application/x-www-form-urlencoded";
            request.PostData = this.keepAliveContext.PostData;
            request.Headers.Add("Accept-Encoding", "gzip,deflate,sdch");
            request.Headers.Add("Accept-Language", "en-US,en;q=0.8");
            request.Headers.Add("Cookie", this.keepAliveContext.Cookie + "; geo=CN");

            ScatterRequest(request);
        }

        public void DoWork()
        {
            while (true)
            {
                this.cookie = string.Empty;
                string responseContent;

                try
                {
                    AutoClickManager.UpdateWorkerStatus("Initial");
                    responseContent = InitialStep();

                    AutoClickManager.UpdateWorkerStatus("Select Store");
                    SelectStoreStep(responseContent);

                    AutoClickManager.UpdateWorkerStatus("Add To Cart");
                    for (int i = 0; i < ConfigHelper.ReserveCount; i++)
                    {
                        AddToCartStep(true);
                    }

                    AutoClickManager.UpdateWorkerStatus("Create Reservation");
                    responseContent = CreateReservationStep();

                    AutoClickManager.UpdateWorkerStatus("Login");
                    responseContent = LoginStep(responseContent);

                    int cartItemQuantity = Regex.Matches(responseContent, "iPhone 4S").Count;
                    if (cartItemQuantity < 1 || cartItemQuantity > 2)
                    {
                        throw new Exception();
                    }
                    AutoClickManager.ShowCartItemQuantity(cartItemQuantity);

                    if (confirmMode == ConfirmMode.SignalMode)
                    {
                        try
                        {
                            suspendDoor.Set();

                            AutoClickManager.UpdateWorkerStatus("Waiting PickDate Door");
                            while (true)
                            {
                                bool isSet = pickDateDoor.Wait(ConfigHelper.WorkerSleep);
                                if (isSet == true)
                                {
                                    break;
                                }
                                else
                                {
                                    KeepSeesionAlive();
                                }
                            }

                            AutoClickManager.UpdateWorkerStatus("Waiting CheckProduct Door");
                            while (true)
                            {
                                bool isSet = checkProductDoor.Wait(ConfigHelper.WorkerSleep);
                                if (isSet == true)
                                {
                                    break;
                                }
                                else
                                {
                                    KeepSeesionAlive();
                                }
                            }
                        }
                        finally
                        {
                            suspendDoor.Reset();
                        }
                    }
                    else
                    {
                        AutoClickManager.UpdateWorkerStatus("Pickup Date");
                        while (true)
                        {
                            timeAndMark = PickupDateStep();
                            if (timeAndMark != null)
                            {
                                break;
                            }
                        }
                    }

                    AutoClickManager.UpdateWorkerStatus("Confirm");
                    ConfirmStep(responseContent, timeAndMark);
                }
                catch (Exception ex)
                {
                }
            }
        }

        public void DoCheckProductWork()
        {
            while (true)
            {
                this.cookie = string.Empty;
                string responseContent;

                try
                {
                    responseContent = InitialStep();
                    while (true)
                    {
                        Thread.Sleep(ConfigHelper.ProductCheckerSleep);
                        string productInfo = SelectStoreStep(responseContent);
                        bool productAvailable = productInfo.Contains(ConfigHelper.CurrentProductID);

                        if (productAvailable == true)
                        {
                            if (checkProductDoor.IsSet == false)
                            {
                                checkProductDoor.Set();
                            }
                        }
                        else
                        {
                            if (checkProductDoor.IsSet == true)
                            {
                                checkProductDoor.Reset();
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                }
            }
        }

        public void DoPickupDateWork()
        {
            while (true)
            {
                this.cookie = string.Empty;
                string responseContent;

                try
                {
                    AutoClickManager.UpdateDatePickerStatus("Initial");
                    responseContent = InitialStep();

                    AutoClickManager.UpdateDatePickerStatus("Select Store");
                    SelectStoreStep(responseContent);

                    AutoClickManager.UpdateDatePickerStatus("Add To Cart");
                    AddToCartStep(true);

                    AutoClickManager.UpdateDatePickerStatus("Create Reservation");
                    responseContent = CreateReservationStep();

                    AutoClickManager.UpdateDatePickerStatus("Login");
                    LoginStep(responseContent);

                    AutoClickManager.UpdateDatePickerStatus("Pickup Date");
                    while (true)
                    {
                        Thread.Sleep(ConfigHelper.DatePickerSleep);
                        string[] timeAndMark = PickupDateStep();
                        if (timeAndMark != null)
                        {
                            AutoClick.timeAndMark = timeAndMark;
                            if (pickDateDoor.IsSet == false)
                            {
                                pickDateDoor.Set();
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                }
            }
        }
    }
}
